/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/interpret/builtins/hosts/base/BaseInterpret_abstract.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/inout/convert/functions.h"

#include <cassert>

namespace simodo::interpret::builtins
{
    namespace 
    {
        using namespace simodo::parser;

        void initializeOperationSwitchboard(std::vector<BaseOperationParser_t> & switchboard)
        {
            switchboard.resize(static_cast<size_t>(BaseOperationCode::LastOperation), nullptr);

            switchboard[static_cast<size_t>(BaseOperationCode::None)]           = BaseOperationParser::parseNone;
            switchboard[static_cast<size_t>(BaseOperationCode::PushConstant)]   = BaseOperationParser::parsePushConstant;
            switchboard[static_cast<size_t>(BaseOperationCode::PushVariable)]   = BaseOperationParser::parsePushVariable;
            switchboard[static_cast<size_t>(BaseOperationCode::ObjectElement)]  = BaseOperationParser::parseObjectElement;
            switchboard[static_cast<size_t>(BaseOperationCode::FunctionCall)]   = BaseOperationParser::parseFunctionCall;
            switchboard[static_cast<size_t>(BaseOperationCode::ProcedureCheck)] = BaseOperationParser::parseProcedureCheck;
            switchboard[static_cast<size_t>(BaseOperationCode::Block)]          = BaseOperationParser::parseBlock;
            switchboard[static_cast<size_t>(BaseOperationCode::Pop)]            = BaseOperationParser::parsePop;
        }
    }

BaseInterpret_abstract::BaseInterpret_abstract(Interpret_interface * inter)
    : _interpret(inter)
    , _machine(inter->files())
{
    initializeOperationSwitchboard(_operation_switchboard);
}

BaseInterpret_abstract::~BaseInterpret_abstract()
{
}

void BaseInterpret_abstract::reset()
{
}

InterpretState BaseInterpret_abstract::before_start() 
{ 
    return InterpretState::Flow; 
}

InterpretState BaseInterpret_abstract::before_finish(InterpretState state) 
{ 
    return state; 
}

// void BaseInterpret_abstract::setFiber(Interpret * inter)
// {
//     assert(inter);
//     _interpret = inter;
// }

bool BaseInterpret_abstract::isOperationExists(simodo::ast::OperationCode operation_code) const
{
    size_t operation_index = static_cast<size_t>(operation_code);

    return operation_index < _operation_switchboard.size() && _operation_switchboard[operation_index] != nullptr;
}

InterpretState BaseInterpret_abstract::performOperation(const ast::Node & op) 
{
    if (!isOperationExists(op.operation()))
        throw bormental::DrBormental("BaseInterpret_abstract::performOperation", 
                        "Invalid operation index " + std::to_string(static_cast<size_t>(op.operation())));

    return _operation_switchboard[static_cast<size_t>(op.operation())](*this,op);
}

void BaseInterpret_abstract::importNamespace(std::u16string name, variable::Object ns, inout::TokenLocation location)
{
    _machine.push({name, ns, location, 
                {{  {variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}, 
                    {variable::SPEC_BUILTIN, true},
                }} });
}

InterpretState BaseInterpret_abstract::executeNone()
{
    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executePushConstant(const inout::Token & constant_value)
{
    _machine.push(_machine.createVariable_implicit(constant_value));

    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executePushValue(const inout::Token & variable_name)
{
    variable::VariableRef v;

    try {
        v = _machine.findVariable(variable_name);
    }
    catch(...) {
        // Перед выходом сохраняем состояние выполнения команды
        _machine.push({variable_name.lexeme(), {}, variable_name.location(), {}}); 
        throw;
    }

    _machine.pushRef(v);

    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executeObjectElement(const inout::Token & dot, const inout::Token & variable_name)
{
    variable::Variable v {variable::null_variable()};

    try {
        v = _machine.getElement_implicit(dot,variable_name);
    }
    catch(...) {
        // Перед выходом сохраняем состояние выполнения команды
        _machine.pop();
        _machine.push({variable_name.lexeme(), {}, variable_name.location()}); 
        throw;
    }

    _machine.pop();
    _machine.push(v);

    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executeFunctionCall(const ast::Node & op)
{
    size_t args_stack_position = _machine.stack().size();

    inter().addFlowLayer(
                op, 
                [this, args_stack_position](FlowLayerInfo flow) {
                    variable::Value v;

                    if (!flow.error_sign /*&& checkInterpretType(InterpretType::Preview)*/) {
                        try {
                            v = _machine.callFunction_implicit(flow.code.token(), args_stack_position);
                        }
                        catch(...) {
                            // Перед выходом восстанавливаем ожидаемое состояние выполнения команды
                            _machine.popAmount(_machine.stack().size() - args_stack_position);
                            _machine.pop();
                            _machine.push({flow.code.token().lexeme(), {}, flow.code.token().location(), {}}); 
                            throw;
                        }
                    }

                    // Чистим параметры функции
                    _machine.popAmount(_machine.stack().size() - args_stack_position);
                    // Чистим функцию
                    _machine.pop();
                    // Добавляем результат функции
                    _machine.push({u"", v, flow.code.token().location()});
                });
    
    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executeProcedureCheck()
{
    _machine.checkProcedureCalled();

    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executeBlock(const ast::Node & )
{
    return InterpretState::Flow;
}

InterpretState BaseInterpret_abstract::executePop()
{
    _machine.pop();

    return InterpretState::Flow;
}

}
